Skip to content

fix(status): slide the analytics window on refresh instead of re-polling a frozen range - #1464

Merged
dawsontoth merged 2 commits into
stagefrom
kyle/analytics-sliding-refresh
Jul 10, 2026
Merged

fix(status): slide the analytics window on refresh instead of re-polling a frozen range#1464
dawsontoth merged 2 commits into
stagefrom
kyle/analytics-sliding-refresh

Conversation

@kylebernhardy

Copy link
Copy Markdown
Member

Fixes #1439 — Status analytics auto-refresh re-polls a frozen time window.

Summary

The analytics [startTime, endTime] window was computed once (Date.now() in a memo) and baked into every panel's react-query key; refetchInterval then re-fetched that same fixed window forever. Charts never advanced past their mount time, and each tick was pure load on the customer's Harper while the "Updated Xs ago" pill implied live data.

Auto-refresh now works by sliding the window: a single clock in StatusTabsInner bumps the existing tick mechanism every refresh period, producing a new window (new query keys → one fresh fetch per panel, keepPreviousData bridging the swap). useAnalyticsRecords no longer polls at all — a fixed window is an immutable snapshot (staleTime: Infinity, gcTime bounded to 60s so stranded snapshots don't pile up). The now-dead refreshIntervalMs is removed from AnalyticsContext and its three consumers.

The clock has three guards (each commented in the effect): hidden browser tabs pause ticking with one catch-up tick on return; an elapsed check coalesces near-simultaneous refresh sources (manual refresh / catch-up vs. scheduled tick); and ticks are skipped while a get_analytics batch is still in flight so a slow Harper doesn't accumulate overlapping POST bursts.

Where to look

  • StatusTabs.tsx — the interval/visibilitychange effect and its guards; this is the behavioral core.
  • useAnalyticsRecords.ts — the no-polling model (staleTime/gcTime reasoning is in comments).
  • New regression tests in __tests__/StatusTabs.sliding-window.test.tsx (window advances per tick, refresh=0 never ticks, hidden-pause + single catch-up + coalescing).

Open notes from cross-model review

  • Dropping the per-panel 0–500ms startup jitter means all ~5–7 panels on a tab fire in the same frame on each tick. Both reviewers judged this acceptable (HTTP/2 multiplexing, same average load); flagging in case you'd rather keep a stagger.
  • The in-flight gate keys off the get_analytics_raw cache prefix, so with two instances' Status pages open simultaneously, either page's in-flight batch defers the other's tick by one period. Conservative by design.

Generated by kAIle (Claude Fable 5).

…ing a frozen range

The [startTime, endTime] analytics window was computed once and baked into
every panel's query key; refetchInterval then re-fetched that same fixed
window forever, so charts never advanced and every tick was pure load on
the customer's Harper while the freshness pill implied live data.

A single clock in StatusTabsInner now slides the window forward each
refresh period — skipping ticks while the browser tab is hidden (one
catch-up tick on return), coalescing near-simultaneous refresh sources,
and holding off while a fetch batch is still in flight. Manual refresh
shares the same path. useAnalyticsRecords no longer polls: a fixed window
is an immutable snapshot (staleTime Infinity, gcTime bounded to a minute
so stranded snapshots don't accumulate), and keepPreviousData bridges the
key swap. The now-unused refreshIntervalMs is removed from
AnalyticsContext and its consumers.

Fixes #1439

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 47.08% 4413 / 9372
🔵 Statements 47.45% 4711 / 9928
🔵 Functions 39.2% 1057 / 2696
🔵 Branches 39.35% 2861 / 7269
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/instance/status/analytics/StatusTabs.tsx 91.93% 86.66% 85.71% 91.22% 43-46, 51-58, 128, 136, 197
src/features/instance/status/analytics/context/AnalyticsContext.tsx 87.5% 50% 100% 100% 34
src/features/instance/status/analytics/hooks/useAnalyticsRecords.ts 95.23% 93.33% 100% 94.73% 122-126
src/features/instance/status/analytics/tabs/ConnectionsPanel.tsx 100% 81.81% 100% 100%
src/features/instance/status/analytics/tabs/MetricPanel.tsx 100% 92.85% 100% 100%
src/features/instance/status/analytics/tabs/StorageTab.tsx 92.3% 83.33% 90% 91.66% 59, 63-66, 95-106
src/features/instance/status/analytics/tabs/__tests__/testHelpers.tsx 83.33% 100% 80% 83.33% 10
Generated in workflow #1424 for commit 1495d47 by the Vitest Coverage Report Action

gemini-code-assist[bot]

This comment was marked as resolved.

Review suggestion (gemini): with tick in the effect deps the interval is
recreated after every refresh, so the next scheduled tick is always
exactly refreshMs after the last actual refresh — the elapsed-time
coalescing guard becomes unnecessary and a manual refresh no longer
delays the next auto-refresh by up to two periods.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylebernhardy
kylebernhardy marked this pull request as ready for review July 10, 2026 17:47
@kylebernhardy
kylebernhardy requested a review from a team as a code owner July 10, 2026 17:47
@dawsontoth
dawsontoth added this pull request to the merge queue Jul 10, 2026
Merged via the queue into stage with commit 1627ce8 Jul 10, 2026
3 checks passed
@dawsontoth
dawsontoth deleted the kyle/analytics-sliding-refresh branch July 10, 2026 19:48

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — the sliding-window fix is sound and safe to merge. Replacing the frozen [startTime, endTime] window with a clock that actually advances on each refresh is the right fix, net-neutral on fetch volume (same one-fetch-per-panel-per-period cadence as before, now returning new data instead of re-polling an identical range), and the tick self-referencing effect dep is a clean way to keep the next scheduled tick exactly refreshMs after the last real refresh.

One follow-up worth a quick look, not blocking:

  • The in-flight gate at StatusTabs.tsx:54 matches on queryClient.isFetching({ queryKey: [ANALYTICS_QUERY_KEY_PREFIX] }), which is a prefix match — so any in-flight analytics query for any instance defers this tab's tick, not just this instance's. It's also untested: the regression suite mocks useAnalyticsRecords away entirely, so the in-flight/dogpile guard — one of the three documented safety mechanisms — has zero assertions on it. Scoping the key by instanceParams.entityId plus one test with the hook left unmocked would close both gaps cheaply.

Two smaller suggestions, each easy: scope that same in-flight query key by instance (as above), and gcTime: 60_000 means a panel revisited after >60s pays a fresh loading spinner — intentional per the code comment, just flagging the visible UX cost.

Nice work on this one — the core mechanism is genuinely clean, and the "immutable window ⇒ immutable query key" property nicely sidesteps needing explicit request cancellation on window slides.

— Claude (Sonnet 5), reviewed via review-queue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Status analytics auto-refresh re-polls a frozen time window

3 participants